package com.example.auth.service.impl;

import com.example.auth.dto.ActivityDTO;
import com.example.auth.dto.CourseDTO;
import com.example.auth.dto.StudentDTO;
import com.example.auth.dto.StudentDetailDTO;
import com.example.auth.entity.Role;
import com.example.auth.entity.User;
import com.example.auth.repository.RoleRepository;
import com.example.auth.repository.UserRepository;
import com.example.auth.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl implements UserService {

    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RoleRepository roleRepository;

    @Override
    public User register(String username, String password) {
        logger.info("Attempting to register user: {}", username);

        // Parameter validation
        if (username == null || username.trim().isEmpty()) {
            logger.warn("Registration failed: Username cannot be empty");
            throw new RuntimeException("Username cannot be empty");
        }

        if (password == null || password.trim().isEmpty()) {
            logger.warn("Registration failed: Password cannot be empty");
            throw new RuntimeException("Password cannot be empty");
        }

        // Username length validation
        if (username.length() < 3 || username.length() > 20) {
            logger.warn("Registration failed: Username length must be between 3 and 20 characters");
            throw new RuntimeException("Username length must be between 3 and 20 characters");
        }

        // Password length validation
        if (password.length() < 6 || password.length() > 20) {
            logger.warn("Registration failed: Password length must be between 6 and 20 characters");
            throw new RuntimeException("Password length must be between 6 and 20 characters");
        }

        // Check if the username already exists
        if (userRepository.findByUsername(username) != null) {
            logger.warn("Registration failed: Username {} already exists", username);
            throw new RuntimeException("Username already exists");
        }

        try {
            // Create a new user
            User user = new User();
            user.setUsername(username);
            // Use the custom utility class to encrypt the password
            user.setPassword(PasswordUtils.encryptPassword(password));

            // Set the default status to active
            user.setStatus("active");

            // Set the default role
            Role userRole = roleRepository.findByName("USER");
            if (userRole == null) {
                logger.warn("USER role not found, will create a new role");
                userRole = new Role();
                userRole.setName("USER");
                roleRepository.save(userRole);
            }
            user.setRoles(Collections.singleton(userRole));

            User savedUser = userRepository.save(user);
            logger.info("User {} registered successfully", username);
            return savedUser;
        } catch (Exception e) {
            logger.error("An exception occurred during user registration", e);
            throw new RuntimeException("Registration failed: " + e.getMessage());
        }
    }

    @Override
    public User register(String username, String password, String realName, String nickname, String email, String phone) {
        logger.info("Attempting to register user with full profile: {}", username);

        // Parameter validation
        if (username == null || username.trim().isEmpty()) {
            logger.warn("Registration failed: Username cannot be empty");
            throw new RuntimeException("Username cannot be empty");
        }

        if (password == null || password.trim().isEmpty()) {
            logger.warn("Registration failed: Password cannot be empty");
            throw new RuntimeException("Password cannot be empty");
        }

        // Username length validation
        if (username.length() < 3 || username.length() > 20) {
            logger.warn("Registration failed: Username length must be between 3 and 20 characters");
            throw new RuntimeException("Username length must be between 3 and 20 characters");
        }

        // Password length validation
        if (password.length() < 6 || password.length() > 20) {
            logger.warn("Registration failed: Password length must be between 6 and 20 characters");
            throw new RuntimeException("Password length must be between 6 and 20 characters");
        }

        // Email validation if provided
        if (email != null && !email.isEmpty() && !email.matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$")) {
            logger.warn("Registration failed: Invalid email format");
            throw new RuntimeException("Invalid email format");
        }

        // Phone validation if provided
        if (phone != null && !phone.isEmpty() && !phone.matches("^[0-9]{10,15}$")) {
            logger.warn("Registration failed: Invalid phone number format");
            throw new RuntimeException("Invalid phone number format");
        }

        // Check if the username already exists
        if (userRepository.findByUsername(username) != null) {
            logger.warn("Registration failed: Username {} already exists", username);
            throw new RuntimeException("Username already exists");
        }

        try {
            // Create a new user
            User user = new User();
            user.setUsername(username);
            // Use the custom utility class to encrypt the password
            user.setPassword(PasswordUtils.encryptPassword(password));
            
            // Set additional user information
            user.setRealName(realName);
            user.setNickname(nickname);
            user.setEmail(email);
            user.setPhone(phone);

            // Set the default status to active
            user.setStatus("active");

            // Set the default role
            Role userRole = roleRepository.findByName("USER");
            if (userRole == null) {
                logger.warn("USER role not found, will create a new role");
                userRole = new Role();
                userRole.setName("USER");
                roleRepository.save(userRole);
            }
            user.setRoles(Collections.singleton(userRole));

            User savedUser = userRepository.save(user);
            logger.info("User {} registered successfully with full profile", username);
            return savedUser;
        } catch (Exception e) {
            logger.error("An exception occurred during user registration with full profile", e);
            throw new RuntimeException("Registration failed: " + e.getMessage());
        }
    }

    @Override
    public User findByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    @Override
    public User findById(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    @Override
    public boolean validatePassword(User user, String password) {
        if (user == null || password == null) {
            logger.warn("Password validation failed: User or password is null");
            return false;
        }

        logger.info("Validating password for user: {}, stored password starts with: {}", 
            user.getUsername(), 
            user.getPassword().length() > 5 ? user.getPassword().substring(0, 5) : user.getPassword());

        // Use the custom utility class to validate the password
        boolean matches = PasswordUtils.matches(password, user.getPassword());

        if (matches) {
            logger.info("Password validation successful for user: {}", user.getUsername());
            // Check if the password needs to be upgraded to the new format
            if (PasswordUtils.needsUpgrade(user.getPassword())) {
                logger.info("Password for user {} is in old format, automatically upgrading to new format", user.getUsername());
                try {
                    // Re-encrypt and save using new format
                    user.setPassword(PasswordUtils.encryptPassword(password));
                    userRepository.save(user);
                    logger.info("Password for user {} has been upgraded to new format", user.getUsername());
                } catch (Exception e) {
                    // If the upgrade fails, only log the error, do not affect the login result
                    logger.error("Password format upgrade failed for user {}", user.getUsername(), e);
                }
            }
            return true;
        } else {
            logger.warn("Password validation failed for user {}: Passwords do not match", user.getUsername());
            return false;
        }
    }

    @Override
    public void changePassword(User user, String currentPassword, String newPassword) {
        // Validate the current password
        if (!validatePassword(user, currentPassword)) {
            logger.warn("Password change failed: Incorrect current password");
            throw new RuntimeException("Incorrect current password");
        }

        // New password validation
        if (newPassword == null || newPassword.trim().isEmpty()) {
            logger.warn("Password change failed: New password cannot be empty");
            throw new RuntimeException("New password cannot be empty");
        }

        if (newPassword.length() < 6 || newPassword.length() > 20) {
            logger.warn("Password change failed: New password length must be between 6 and 20 characters");
            throw new RuntimeException("New password length must be between 6 and 20 characters");
        }

        try {
            // Use the custom utility class to encrypt the new password
            user.setPassword(PasswordUtils.encryptPassword(newPassword));
            userRepository.save(user);
            logger.info("Password for user {} changed successfully", user.getUsername());
        } catch (Exception e) {
            logger.error("An exception occurred during password change", e);
            throw new RuntimeException("Password change failed: " + e.getMessage());
        }
    }

    @Override
    public void updateAvatar(User user, String avatarUrl) {
        if (user == null) {
            logger.warn("Avatar update failed: User is null");
            throw new RuntimeException("User does not exist");
        }

        try {
            user.setAvatar(avatarUrl);
            userRepository.save(user);
            logger.info("Avatar for user {} updated successfully", user.getUsername());
        } catch (Exception e) {
            logger.error("An exception occurred during avatar update", e);
            throw new RuntimeException("Avatar update failed: " + e.getMessage());
        }
    }

    @Override
    public User updateProfile(User user, Map<String, String> profileData) {
        logger.info("Updating profile for user {}", user.getUsername());
        
        try {
            // Update user information if available
            String realName = profileData.get("realName");
            if (realName != null) {
                user.setRealName(realName);
            }
            
            String nickname = profileData.get("nickname");
            if (nickname != null) {
                user.setNickname(nickname);
            }
            
            String email = profileData.get("email");
            if (email != null) {
                user.setEmail(email);
            }
            
            String phone = profileData.get("phone");
            if (phone != null) {
                user.setPhone(phone);
            }
            
            String bio = profileData.get("bio");
            if (bio != null) {
                user.setBio(bio);
            }
            
            User updatedUser = userRepository.save(user);
            logger.info("Profile for user {} updated successfully", user.getUsername());
            return updatedUser;
        } catch (Exception e) {
            logger.error("An exception occurred during profile update", e);
            throw new RuntimeException("Profile update failed: " + e.getMessage());
        }
    }

    // ==================== Student Management Related Method Implementations ====================

    @Override
    public List<StudentDTO> getAllStudents() {
        logger.info("Getting all student list");
        try {
            // Get USER role
            Role userRole = roleRepository.findByName("USER");
            if (userRole == null) {
                logger.warn("USER role not found");
                return new ArrayList<>();
            }

            // Get all users with the USER role, and ensure role information is complete
            List<User> users = userRepository.findAll();
            return users.stream()
                    // Ensure that the user has role information and includes the USER role
                    .filter(user -> user != null && user.getRoles() != null && !user.getRoles().isEmpty())
                    .filter(user -> user.getRoles().contains(userRole) &&
                            !user.getRoles().stream()
                                    .anyMatch(role -> "ADMIN".equals(role.getName()) || "SUPERVISOR".equals(role.getName())))
                    .map(this::convertToStudentDTO)
                    .collect(Collectors.toList());
        } catch (Exception e) {
            logger.error("An exception occurred while getting the student list", e);
            throw new RuntimeException("Failed to get student list: " + e.getMessage());
        }
    }

    @Override
    public StudentDetailDTO getStudentDetails(Long id) {
        logger.info("Getting student details, ID: {}", id);
        try {
            Optional<User> userOpt = userRepository.findById(id);
            if (!userOpt.isPresent()) {
                logger.warn("Student with ID {} not found", id);
                throw new RuntimeException("Student does not exist");
            }

            User user = userOpt.get();

            // Check if the user is a student (has USER role)
            Role userRole = roleRepository.findByName("USER");
            if (!user.getRoles().contains(userRole)) {
                logger.warn("User with ID {} is not a student", id);
                throw new RuntimeException("Specified ID user is not a student");
            }

            // Convert to StudentDTO
            StudentDTO studentDTO = convertToStudentDTO(user);

            // Generate course and activity data
            List<CourseDTO> courses = generateCoursesForStudent(user);
            List<ActivityDTO> activities = generateActivitiesForStudent(user);

            // Create and return StudentDetailDTO
            return StudentDetailDTO.fromStudentDTO(studentDTO, courses, activities);
        } catch (Exception e) {
            logger.error("An exception occurred while getting student details", e);
            throw new RuntimeException("Failed to get student details: " + e.getMessage());
        }
    }

    @Override
    public boolean updateStudentStatus(Long id, String status) {
        logger.info("Updating student status, ID: {}, New status: {}", id, status);
        try {
            Optional<User> userOpt = userRepository.findById(id);
            if (!userOpt.isPresent()) {
                logger.warn("Student with ID {} not found", id);
                return false;
            }

            User user = userOpt.get();

            // Check if the user is a student (has USER role)
            Role userRole = roleRepository.findByName("USER");
            if (!user.getRoles().contains(userRole)) {
                logger.warn("User with ID {} is not a student", id);
                return false;
            }

            // Validate status value
            if (!"active".equals(status) && !"inactive".equals(status)) {
                logger.warn("Invalid status value: {}", status);
                return false;
            }

            // Update status
            user.setStatus(status);
            userRepository.save(user);
            logger.info("Student {} status updated to {}", user.getUsername(), status);
            return true;
        } catch (Exception e) {
            logger.error("An exception occurred while updating student status", e);
            return false;
        }
    }

    @Override
    public boolean deleteStudent(Long id) {
        logger.info("Deleting student, ID: {}", id);
        try {
            Optional<User> userOpt = userRepository.findById(id);
            if (!userOpt.isPresent()) {
                logger.warn("Student with ID {} not found", id);
                return false;
            }

            User user = userOpt.get();

            // Check if the user is a student (has USER role)
            Role userRole = roleRepository.findByName("USER");
            if (!user.getRoles().contains(userRole)) {
                logger.warn("User with ID {} is not a student", id);
                return false;
            }

            // Delete the user
            userRepository.delete(user);
            logger.info("Student {} deleted", user.getUsername());
            return true;
        } catch (Exception e) {
            logger.error("An exception occurred while deleting a student", e);
            return false;
        }
    }

    @Override
    public List<User> getAllSupervisors() {
        // Get SUPERVISOR role
        Role supervisorRole = roleRepository.findByName("SUPERVISOR");
        if (supervisorRole == null) {
            logger.warn("SUPERVISOR role not found");
            return Collections.emptyList();
        }

        // Find all users with the SUPERVISOR role
        List<User> allUsers = userRepository.findAll();
        return allUsers.stream()
                .filter(user -> user.getRoles().contains(supervisorRole))
                .collect(Collectors.toList());
    }

    @Override
    public StudentDTO assignSupervisorToStudent(Long studentId, Long supervisorId) {
        logger.info("Assigning student to supervisor, Student ID: {}, Supervisor ID: {}", studentId, supervisorId);
        
        try {
            // Verify student exists
            Optional<User> studentOpt = userRepository.findById(studentId);
            if (!studentOpt.isPresent()) {
                logger.warn("Student with ID {} not found", studentId);
                throw new RuntimeException("Student not found");
            }
            
            User student = studentOpt.get();
            
            // Verify the user is actually a student (has USER role)
            Role userRole = roleRepository.findByName("USER");
            if (!student.getRoles().contains(userRole)) {
                logger.warn("User with ID {} is not a student", studentId);
                throw new RuntimeException("The selected user is not a student");
            }
            
            // Verify supervisor exists
            if (supervisorId != null) {
                Optional<User> supervisorOpt = userRepository.findById(supervisorId);
                if (!supervisorOpt.isPresent()) {
                    logger.warn("Supervisor with ID {} not found", supervisorId);
                    throw new RuntimeException("Supervisor not found");
                }
                
                User supervisor = supervisorOpt.get();
                
                // Verify the user is actually a supervisor (has SUPERVISOR role)
                Role supervisorRole = roleRepository.findByName("SUPERVISOR");
                if (!supervisor.getRoles().contains(supervisorRole)) {
                    logger.warn("User with ID {} is not a supervisor", supervisorId);
                    throw new RuntimeException("The selected user is not a supervisor");
                }
            }
            
            // Set the student's supervisorId
            student.setSupervisorId(supervisorId);
            
            // Save the update
            User savedStudent = userRepository.save(student);
            logger.info("Student {} successfully assigned to supervisor {}", studentId, supervisorId);
            
            // Return the updated StudentDTO
            return convertToStudentDTO(savedStudent);
        } catch (Exception e) {
            logger.error("Failed to assign student to supervisor", e);
            throw new RuntimeException("Failed to assign student to supervisor: " + e.getMessage());
        }
    }

    @Override
    public List<StudentDTO> getSupervisorStudents(Long supervisorId) {
        logger.info("Getting students for supervisor, Supervisor ID: {}", supervisorId);
        
        // Verify supervisor exists
        Optional<User> supervisorOpt = userRepository.findById(supervisorId);
        if (!supervisorOpt.isPresent()) {
            logger.warn("Supervisor with ID {} not found", supervisorId);
            throw new RuntimeException("Supervisor not found");
        }
        
        User supervisor = supervisorOpt.get();
        
        // Verify the user is actually a supervisor (has SUPERVISOR role)
        Role supervisorRole = roleRepository.findByName("SUPERVISOR");
        if (!supervisor.getRoles().contains(supervisorRole)) {
            logger.warn("User with ID {} is not a supervisor", supervisorId);
            throw new RuntimeException("The requested user is not a supervisor");
        }
        
        // Find all students supervised by this supervisor
        List<User> allUsers = userRepository.findAll();
        List<User> supervisorStudents = allUsers.stream()
                .filter(user -> {
                    // Check if the user is a student
                    Role userRole = roleRepository.findByName("USER");
                    return user.getRoles().contains(userRole) && 
                           // Check if the student is supervised by this supervisor
                           supervisorId.equals(user.getSupervisorId());
                })
                .collect(Collectors.toList());
        
        // Convert to DTO list and return
        return supervisorStudents.stream()
                .map(this::convertToStudentDTO)
                .collect(Collectors.toList());
    }

    // ==================== Helper Methods ====================

    /**
     * Converts a User entity to a StudentDTO
     */
    private StudentDTO convertToStudentDTO(User user) {
        String displayName = user.getRealName() != null ? user.getRealName() : user.getUsername();
        String lastLoginStr = formatTime(user.getCreateTime());

        // Use student ID to generate random progress (0-100)
        Random random = new Random(user.getId());
        int progress = random.nextInt(101);

        return new StudentDTO(
                user.getId(),
                displayName,
                user.getUsername(), // Use username as student ID temporarily
                "Computer " + ((user.getId().intValue() % 3) + 1) + " Class", // Simulate class
                user.getEmail(),
                user.getPhone(),
                user.getStatus(),
                lastLoginStr,
                progress,
                user.getSupervisorId()
        );
    }

    /**
     * Formats time into a relative time description
     */
    private String formatTime(LocalDateTime time) {
        if (time == null) {
            return "Never logged in";
        }

        LocalDateTime now = LocalDateTime.now();
        long daysDiff = java.time.Duration.between(time, now).toDays();

        if (daysDiff == 0) {
            return "Today " + time.format(DateTimeFormatter.ofPattern("HH:mm"));
        } else if (daysDiff == 1) {
            return "Yesterday " + time.format(DateTimeFormatter.ofPattern("HH:mm"));
        } else if (daysDiff < 7) {
            return daysDiff + " days ago";
        } else {
            return time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        }
    }

    /**
     * Generates course data for a student
     */
    private List<CourseDTO> generateCoursesForStudent(User user) {
        List<CourseDTO> courses = new ArrayList<>();

        // Use user ID as a random seed, ensuring the same user always gets the same course data
        Random random = new Random(user.getId());

        // Add Advanced Mathematics course
        int mathCompletion = 10 + random.nextInt(91); // 10-100
        courses.add(new CourseDTO(
                "Advanced Mathematics",
                mathCompletion == 100 ? "Completed" : "In Progress",
                "Completed Chapter " + (mathCompletion / 20 + 1),
                mathCompletion
        ));

        // Add Data Structures course
        int dataStructureCompletion = random.nextInt(91); // 0-90
        courses.add(new CourseDTO(
                "Data Structures",
                "In Progress",
                "Completed Chapter " + (dataStructureCompletion / 20 + 1),
                dataStructureCompletion
        ));

        // Add College English course
        int englishCompletion = 20 + random.nextInt(81); // 20-100
        courses.add(new CourseDTO(
                "College English",
                englishCompletion == 100 ? "Completed" : "In Progress",
                englishCompletion == 100 ? "All Completed" : "Completed Chapter " + (englishCompletion / 20 + 1),
                englishCompletion
        ));

        return courses;
    }

    /**
     * Generates activity data for a student
     */
    private List<ActivityDTO> generateActivitiesForStudent(User user) {
        List<ActivityDTO> activities = new ArrayList<>();

        // Use user ID as a random seed, ensuring the same user always gets the same activity data
        Random random = new Random(user.getId());
        LocalDateTime now = LocalDateTime.now();

        // Determine if the student has been active recently
        if ("active".equals(user.getStatus())) {
            // Add activity for logging into the system
            activities.add(new ActivityDTO(
                    "info",
                    "Logged into System",
                    "Logged in via " + (random.nextBoolean() ? "Mobile" : "Computer"),
                    formatTime(now.minusHours(random.nextInt(24)))
            ));

            // Add activity for submitting homework or taking an exam
            if (random.nextBoolean()) {
                activities.add(new ActivityDTO(
                        "success",
                        "Submitted Homework",
                        "Submitted Chapter " + (random.nextInt(5) + 1) + " homework for 《" + (random.nextBoolean() ? "Advanced Mathematics" : "Data Structures") + "》",
                        formatTime(now.minusHours(random.nextInt(48)))
                ));
            } else {
                activities.add(new ActivityDTO(
                        "success",
                        "Took Exam",
                        "Completed the midterm exam for 《" + (random.nextBoolean() ? "Advanced Mathematics" : "Data Structures") + "》",
                        formatTime(now.minusDays(random.nextInt(3)))
                ));
            }
        } else {
            // Inactive user, add absenteeism notification
            activities.add(new ActivityDTO(
                    "warning",
                    "Absenteeism Notification",
                    "Has not logged into the system for " + (random.nextInt(5) + 3) + " consecutive days",
                    formatTime(now)
            ));

            // Add last login record
            activities.add(new ActivityDTO(
                    "info",
                    "Logged into System",
                    "Logged in via " + (random.nextBoolean() ? "Mobile" : "Computer"),
                    formatTime(now.minusDays(random.nextInt(7) + 3))
            ));
        }

        // May add a homework overdue notification
        if (random.nextInt(3) == 0) { // 1/3 probability
            activities.add(new ActivityDTO(
                    "warning",
                    "Overdue Homework",
                    "Chapter " + (random.nextInt(5) + 1) + " homework for 《" + (random.nextBoolean() ? "Advanced Mathematics" : "Data Structures") + "》 is overdue",
                    formatTime(now.minusDays(random.nextInt(3) + 1))
            ));
        }

        return activities;
    }
}